#include "U2GLESHardwarePixelBuffer.h"

// #include "OgreGLESRenderTexture.h"
#include "U2Exception.h"
#include "U2DataStreamMemory.h"
#include "U2GLESPixelUtil.h"
#include "U2Texture.h"
#include "U2GLESFBORenderTexture.h"
#include "U2Root.h"
#include "U2RenderSystem.h"


static int computeLog(GLuint value)
{
    int i;

    i = 0;

    /* Error! */
    if (value == 0) return -1;

    for (;;)
    {
        if (value & 1)
        {
            /* Error! */
            if (value != 1) return -1;
                return i;
        }
        value = value >> 1;
        i++;
    }
}



U2EG_NAMESPACE_USING



GLESHardwarePixelBuffer::GLESHardwarePixelBuffer(size_t mWidth, size_t mHeight,
                                                 size_t mDepth, PixelFormat mFormat,
                                                 U2HardwareBuffer::Usage usage)
: U2HardwarePixelBuffer(mWidth, mHeight, mDepth, mFormat, usage, false, false)
, mBuffer(mWidth, mHeight, mDepth, mFormat)
, mGLInternalFormat(0)
{
}

GLESHardwarePixelBuffer::~GLESHardwarePixelBuffer()
{
    // Force free buffer
    delete [] (u2uint8*)mBuffer.data;
}

void GLESHardwarePixelBuffer::allocateBuffer()
{
    if (mBuffer.data)
        // Already allocated
        return;

    mBuffer.data = new u2uint8[mSizeInBytes];
    // TODO use PBO if we're HBU_DYNAMIC
}

void GLESHardwarePixelBuffer::freeBuffer()
{
    // Free buffer if we're STATIC to save memory
    if (mUsage & HBU_STATIC)
    {
        delete [] (u2uint8*)mBuffer.data;
        mBuffer.data = 0;
    }
}

U2PixelBox GLESHardwarePixelBuffer::lockImpl(const U2Image::Box lockBox,  LockOptions options)
{
    allocateBuffer();
    if (options != U2HardwareBuffer::HBL_DISCARD &&
        (mUsage & U2HardwareBuffer::HBU_WRITE_ONLY) == 0)
    {
        // Download the old contents of the texture
        download(mBuffer);
    }
    mCurrentLockOptions = options;
    mLockedBox = lockBox;
    return mBuffer.getSubVolume(lockBox);
}

void GLESHardwarePixelBuffer::unlockImpl(void)
{
    if (mCurrentLockOptions != U2HardwareBuffer::HBL_READ_ONLY)
    {
        // From buffer to card, only upload if was locked for writing
        upload(mCurrentLock, mLockedBox);
    }
    freeBuffer();
}

void GLESHardwarePixelBuffer::blitFromMemory(const U2PixelBox &src, const U2Image::Box &dstBox)
{
    if (!mBuffer.contains(dstBox))
    {
        U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS,
                    "Destination box out of range",
                    "GLESHardwarePixelBuffer::blitFromMemory");
    }

    U2PixelBox scaled;

    if (src.getWidth() != dstBox.getWidth() ||
        src.getHeight() != dstBox.getHeight() ||
        src.getDepth() != dstBox.getDepth())
    {
        // Scale to destination size. Use DevIL and not iluScale because ILU screws up for 
        // floating point textures and cannot cope with 3D images.
        // This also does pixel format conversion if needed
        allocateBuffer();
        scaled = mBuffer.getSubVolume(dstBox);
        U2Image::scale(src, scaled, U2Image::FILTER_BILINEAR);
    }
    else if ((src.format != mFormat) ||
             ((U2GLESPixelUtil::getGLOriginFormat(src.format) == 0) && (src.format != PF_R8G8B8)))
    {
        // Extents match, but format is not accepted as valid source format for GL
        // do conversion in temporary buffer
        allocateBuffer();
        scaled = mBuffer.getSubVolume(dstBox);
        U2PixelUtil::bulkPixelConversion(src, scaled);

        if(mFormat == PF_A4R4G4B4)
        {
            // ARGB->BGRA
            U2GLESPixelUtil::convertToGLformat(scaled, scaled);
        }
    }
    else
    {
        allocateBuffer();
        scaled = src;

        if (src.format == PF_R8G8B8)
        {
            scaled.format = PF_B8G8R8;
            U2PixelUtil::bulkPixelConversion(src, scaled);
        }
    }

    upload(scaled, dstBox);
    freeBuffer();
}

void GLESHardwarePixelBuffer::blitToMemory(const U2Image::Box &srcBox, const U2PixelBox &dst)
{
    if (!mBuffer.contains(srcBox))
    {
        U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS,
                    "source box out of range",
                    "GLESHardwarePixelBuffer::blitToMemory");
    }

    if (srcBox.left == 0 && srcBox.right == getWidth() &&
        srcBox.top == 0 && srcBox.bottom == getHeight() &&
        srcBox.front == 0 && srcBox.back == getDepth() &&
        dst.getWidth() == getWidth() &&
        dst.getHeight() == getHeight() &&
        dst.getDepth() == getDepth() &&
        U2GLESPixelUtil::getGLOriginFormat(dst.format) != 0)
    {
        // The direct case: the user wants the entire texture in a format supported by GL
        // so we don't need an intermediate buffer
        download(dst);
    }
    else
    {
        // Use buffer for intermediate copy
        allocateBuffer();
        // Download entire buffer
        download(mBuffer);
        if(srcBox.getWidth() != dst.getWidth() ||
            srcBox.getHeight() != dst.getHeight() ||
            srcBox.getDepth() != dst.getDepth())
        {
            // We need scaling
            U2Image::scale(mBuffer.getSubVolume(srcBox), dst, U2Image::FILTER_BILINEAR);
        }
        else
        {
            // Just copy the bit that we need
            U2PixelUtil::bulkPixelConversion(mBuffer.getSubVolume(srcBox), dst);
        }
        freeBuffer();
    }
}

void GLESHardwarePixelBuffer::upload(const U2PixelBox &data, const U2Image::Box &dest)
{
    U2_EXCEPT(U2Exception::ERR_RENDERINGAPI_ERROR,
                "Upload not possible for this pixelbuffer type",
                "GLESHardwarePixelBuffer::upload");
}

void GLESHardwarePixelBuffer::download(const U2PixelBox &data)
{
    U2_EXCEPT(U2Exception::ERR_RENDERINGAPI_ERROR,
                "Download not possible for this pixelbuffer type",
                "GLESHardwarePixelBuffer::download");
}

void GLESHardwarePixelBuffer::bindToFramebuffer(GLenum attachment, size_t zoffset)
{
    U2_EXCEPT(U2Exception::ERR_RENDERINGAPI_ERROR,
                "Framebuffer bind not possible for this pixelbuffer type",
                "GLESHardwarePixelBuffer::bindToFramebuffer");
}

// TextureBuffer
GLESTextureBuffer::GLESTextureBuffer(const U2String &baseName, GLenum target, GLuint id, 
                                     GLint width, GLint height, GLint internalFormat, GLenum format,
                                     GLint face, GLint level, Usage usage, bool crappyCard, 
                                     bool writeGamma, u2uint fsaa)
: GLESHardwarePixelBuffer(0, 0, 0, PF_UNKNOWN, usage)
, mTarget(target)
, mTextureID(id)
, mFace(face)
, mLevel(level)
, mSoftwareMipmap(crappyCard)
{
    GL_CHECK_ERROR;
    glBindTexture(GL_TEXTURE_2D, mTextureID);
    GL_CHECK_ERROR;

    // Get face identifier
    mFaceTarget = mTarget;

    // Calculate the width and height of the texture at this mip level
    mWidth = mLevel == 0 ? width : width / pow(2.0f, level);
    mHeight = mLevel == 0 ? height : height / pow(2.0f, level);
    if(mWidth < 1)
        mWidth = 1;
    if(mHeight < 1)
        mHeight = 1;
    
    // Only 2D is supported so depth is always 1
    mDepth = 1;

    mGLInternalFormat = internalFormat;
    mFormat = U2GLESPixelUtil::getClosestOGREFormat(internalFormat, format);

    mRowPitch = mWidth;
    mSlicePitch = mHeight*mWidth;
    mSizeInBytes = U2PixelUtil::getMemorySize(mWidth, mHeight, mDepth, mFormat);

    // Log a message
//        std::stringstream str;
//        str << "GLESHardwarePixelBuffer constructed for texture " << mTextureID 
//            << " face " << mFace << " level " << mLevel << ": "
//            << "width=" << mWidth << " height="<< mHeight << " depth=" << mDepth
//            << "format=" << U2PixelUtil::getFormatName(mFormat);
//        LogManager::getSingleton().logMessage(LML_NORMAL, str.str());

    // Set up a pixel box
    mBuffer = U2PixelBox(mWidth, mHeight, mDepth, mFormat);
    
    if (mWidth==0 || mHeight==0 || mDepth==0)
        // We are invalid, do not allocate a buffer
        return;

    /**********************@@@@@@@@@@@@@@@@@@@@@
    // Is this a render target?
    if (mUsage & TU_RENDERTARGET)
    {
        // Create render target for each slice
        mSliceTRT.reserve(mDepth);
        for(size_t zoffset=0; zoffset<mDepth; ++zoffset)
        {
            U2String name;
            name = "rtt/" + U2StringConverter::toString((size_t)this) + "/" + baseName;
            GLESSurfaceDesc target;
            target.buffer = this;
            target.zoffset = zoffset;
            RenderTexture *trt = GLESRTTManager::getSingleton().createRenderTexture(name, target, writeGamma, fsaa);
            mSliceTRT.push_back(trt);
            U2Root::getSingleton().getRenderSystem()->attachRenderTarget(*mSliceTRT[zoffset]);
        }
    }
    */
}

GLESTextureBuffer::~GLESTextureBuffer()
{
    /**********************@@@@@@@@@@@@@@@@@@@@@
    if (mUsage & TU_RENDERTARGET)
    {
        // Delete all render targets that are not yet deleted via _clearSliceRTT because the rendertarget
        // was deleted by the user.
        for (SliceTRT::const_iterator it = mSliceTRT.begin(); it != mSliceTRT.end(); ++it)
        {
            U2Root::getSingleton().getRenderSystem()->destroyRenderTarget((*it)->getName());
        }
    }
    */
}

void GLESTextureBuffer::upload(const U2PixelBox &data, const U2Image::Box &dest)
{
    glBindTexture(mTarget, mTextureID);
    GL_CHECK_ERROR;

    if (U2PixelUtil::isCompressed(data.format))
    {
        if(data.format != mFormat || !data.isConsecutive())
            U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS,
                        "Compressed images must be consecutive, in the source format",
                        "GLESTextureBuffer::upload");

        GLenum format = U2GLESPixelUtil::getClosestGLInternalFormat(mFormat);
        // Data must be consecutive and at beginning of buffer as PixelStorei not allowed
        // for compressed formats
        if (dest.left == 0 && dest.top == 0)
        {
            glCompressedTexImage2D(GL_TEXTURE_2D, mLevel,
                                   format,
                                   dest.getWidth(),
                                   dest.getHeight(),
                                   0,
                                   data.getConsecutiveSize(),
                                   data.data);
            GL_CHECK_ERROR;
        }
        else
        {
            glCompressedTexSubImage2D(GL_TEXTURE_2D, mLevel,
                                      dest.left, dest.top,
                                      dest.getWidth(), dest.getHeight(),
                                      format, data.getConsecutiveSize(),
                                      data.data);

            GL_CHECK_ERROR;
        }
    }
    else if (mSoftwareMipmap)
    {
        if (data.getWidth() != data.rowPitch)
        {
            // TODO
            U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS,
                        "Unsupported texture format",
                        "GLESTextureBuffer::upload");
        }

        if (data.getHeight() * data.getWidth() != data.slicePitch)
        {
            // TODO
            U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS,
                        "Unsupported texture format",
                        "GLESTextureBuffer::upload");
        }

        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        GL_CHECK_ERROR;
        buildMipmaps(data);
    }
    else
    {
        if(data.getWidth() != data.rowPitch)
        {
            // TODO
            U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS,
                        "Unsupported texture format",
                        "GLESTextureBuffer::upload");
        }

        if(data.getHeight()*data.getWidth() != data.slicePitch)
        {
            // TODO
            U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS,
                        "Unsupported texture format",
                        "GLESTextureBuffer::upload");
        }

        if ((data.getWidth() * U2PixelUtil::getNumElemBytes(data.format)) & 3) {
            // Standard alignment of 4 is not right
            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
            GL_CHECK_ERROR;
        }

        glTexSubImage2D(mFaceTarget,
                        mLevel,
                        dest.left, dest.top,
                        dest.getWidth(), dest.getHeight(),
                        U2GLESPixelUtil::getGLOriginFormat(data.format),
                        U2GLESPixelUtil::getGLOriginDataType(data.format),
                        data.data);
        GL_CHECK_ERROR;
    }

    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
    GL_CHECK_ERROR;
}

//-----------------------------------------------------------------------------  
void GLESTextureBuffer::download(const U2PixelBox &data)
{
    U2_EXCEPT(U2Exception::ERR_NOT_IMPLEMENTED, 
                "Downloading texture buffers is not supported by OpenGL ES",
                "GLESTextureBuffer::download");
}
//-----------------------------------------------------------------------------  
void GLESTextureBuffer::bindToFramebuffer(GLenum attachment, size_t zoffset)
{
    assert(zoffset < mDepth);
    glFramebufferTexture2DOES(GL_FRAMEBUFFER_OES, attachment,
                              mFaceTarget, mTextureID, mLevel);
    GL_CHECK_ERROR;
}

void GLESTextureBuffer::copyFromFramebuffer(size_t zoffset)
{
    glBindTexture(GL_TEXTURE_2D, mTextureID);
    GL_CHECK_ERROR;
    glCopyTexSubImage2D(GL_TEXTURE_2D, mLevel, 0, 0, 0, 0, mWidth, mHeight);
    GL_CHECK_ERROR;
}

//-----------------------------------------------------------------------------  
void GLESTextureBuffer::blit(const U2HardwarePixelBufferSharedPtr &src, const U2Image::Box &srcBox, const U2Image::Box &dstBox)
{
    GLESTextureBuffer *srct = static_cast<GLESTextureBuffer *>(src.getPointer());
    // TODO: Check for FBO support first
    // Destination texture must be 2D
    // Source texture must be 2D
    if((src->getUsage() & TU_RENDERTARGET) == 0 && (srct->mTarget == GL_TEXTURE_2D))
    {
        blitFromTexture(srct, srcBox, dstBox);
    }
    else
    {
        GLESHardwarePixelBuffer::blit(src, srcBox, dstBox);
    }
}

//-----------------------------------------------------------------------------  
// Very fast texture-to-texture blitter and hardware bi/trilinear scaling implementation using FBO
// Destination texture must be 2D
// Source texture must be 2D
// Supports compressed formats as both source and destination format, it will use the hardware DXT compressor
// if available.
// @author W.J. van der Laan
void GLESTextureBuffer::blitFromTexture(GLESTextureBuffer *src, const U2Image::Box &srcBox, const U2Image::Box &dstBox)
{
    if(U2Root::getSingleton().getRenderSystem()->getCapabilities()->hasCapability(RSC_FBO) == false)
    {
        // the following code depends on FBO support, it crashes if FBO is not supported.
        // TODO - write PBUFFER version of this function or a version that doesn't require FBO
        return; // for now - do nothing.
    }

    //        std::cerr << "GLESTextureBuffer::blitFromTexture " <<
    //        src->mTextureID << ":" << srcBox.left << "," << srcBox.top << "," << srcBox.right << "," << srcBox.bottom << " " << 
    //        mTextureID << ":" << dstBox.left << "," << dstBox.top << "," << dstBox.right << "," << dstBox.bottom << std::endl;

    // Store reference to FBO manager
    GLESFBOManager *fboMan = static_cast<GLESFBOManager *>(U2GLESRTTManager::getSingletonPtr());

    // Save and clear GL state for rendering
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |  GL_STENCIL_BUFFER_BIT);

    U2RenderSystem* rsys = U2Root::getSingleton().getRenderSystem();
    rsys->_disableTextureUnitsFrom(0);

    // Disable alpha, depth and scissor testing, disable blending, 
    // disable culling, disble lighting, disable fog and reset foreground
    // colour.
    glDisable(GL_ALPHA_TEST);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_SCISSOR_TEST);
    glDisable(GL_BLEND);
    glDisable(GL_CULL_FACE);
    glDisable(GL_LIGHTING);
    glDisable(GL_FOG);
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    GL_CHECK_ERROR;

    // Save and reset matrices
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_TEXTURE);
    glPushMatrix();
    glLoadIdentity();
    GL_CHECK_ERROR;

    // Set up source texture
    glBindTexture(src->mTarget, src->mTextureID);
    GL_CHECK_ERROR;

    // Set filtering modes depending on the dimensions and source
    if(srcBox.getWidth()==dstBox.getWidth() &&
        srcBox.getHeight()==dstBox.getHeight() &&
        srcBox.getDepth()==dstBox.getDepth())
    {
        // Dimensions match -- use nearest filtering (fastest and pixel correct)
        glTexParameteri(src->mTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        GL_CHECK_ERROR;
        glTexParameteri(src->mTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        GL_CHECK_ERROR;
    }
    else
    {
        // Dimensions don't match -- use bi or trilinear filtering depending on the
        // source texture.
        if(src->mUsage & TU_AUTOMIPMAP)
        {
            // Automatic mipmaps, we can safely use trilinear filter which
            // brings greatly imporoved quality for minimisation.
            glTexParameteri(src->mTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
            GL_CHECK_ERROR;
            glTexParameteri(src->mTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);    
            GL_CHECK_ERROR;
        }
        else
        {
            // Manual mipmaps, stay safe with bilinear filtering so that no
            // intermipmap leakage occurs.
            glTexParameteri(src->mTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
            GL_CHECK_ERROR;
            glTexParameteri(src->mTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            GL_CHECK_ERROR;
        }
    }
    // Clamp to edge (fastest)
    glTexParameteri(src->mTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    GL_CHECK_ERROR;
    glTexParameteri(src->mTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    GL_CHECK_ERROR;

    // Store old binding so it can be restored later
    GLint oldfb;
    glGetIntegerv(GL_FRAMEBUFFER_BINDING_OES, &oldfb);
    GL_CHECK_ERROR;

    // Set up temporary FBO
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, fboMan->getTemporaryFBO());
    GL_CHECK_ERROR;

    GLuint tempTex = 0;
    if(!fboMan->checkFormat(mFormat))
    {
        // If target format not directly supported, create intermediate texture
        GLenum tempFormat = U2GLESPixelUtil::getClosestGLInternalFormat(fboMan->getSupportedAlternative(mFormat));
        glGenTextures(1, &tempTex);
        GL_CHECK_ERROR;
        glBindTexture(GL_TEXTURE_2D, tempTex);
        GL_CHECK_ERROR;
        // Allocate temporary texture of the size of the destination area
        glTexImage2D(GL_TEXTURE_2D, 0, tempFormat, 
            U2GLESPixelUtil::optionalPO2(dstBox.getWidth()), U2GLESPixelUtil::optionalPO2(dstBox.getHeight()), 
            0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
        GL_CHECK_ERROR;
        glFramebufferTexture2DOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
            GL_TEXTURE_2D, tempTex, 0);
        GL_CHECK_ERROR;
        // Set viewport to size of destination slice
        glViewport(0, 0, dstBox.getWidth(), dstBox.getHeight());
        GL_CHECK_ERROR;
    }
    else
    {
        // We are going to bind directly, so set viewport to size and position of destination slice
        glViewport(dstBox.left, dstBox.top, dstBox.getWidth(), dstBox.getHeight());
        GL_CHECK_ERROR;
    }

    // Process each destination slice
    for(size_t slice=dstBox.front; slice<dstBox.back; ++slice)
    {
        if(!tempTex)
        {
            /// Bind directly
            bindToFramebuffer(GL_COLOR_ATTACHMENT0_OES, slice);
        }

        if(tempTex)
        {
            // Copy temporary texture
            glBindTexture(mTarget, mTextureID);
            GL_CHECK_ERROR;
            switch(mTarget)
            {
            case GL_TEXTURE_2D:
                glCopyTexSubImage2D(mFaceTarget, mLevel, 
                    dstBox.left, dstBox.top, 
                    0, 0, dstBox.getWidth(), dstBox.getHeight());
                GL_CHECK_ERROR;
                break;
            }
        }
    }
    // Finish up 
    if(!tempTex)
    {
        // Generate mipmaps
        if(mUsage & TU_AUTOMIPMAP)
        {
            glBindTexture(mTarget, mTextureID);
            GL_CHECK_ERROR;
            glGenerateMipmapOES(mTarget);
            GL_CHECK_ERROR;
        }
    }

    // Reset source texture to sane state
    glBindTexture(src->mTarget, src->mTextureID);
    GL_CHECK_ERROR;

    // Detach texture from temporary framebuffer
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
        GL_RENDERBUFFER_OES, 0);
    GL_CHECK_ERROR;
    // Restore old framebuffer
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, oldfb);
    GL_CHECK_ERROR;
    // Restore matrix stacks and render state
    glMatrixMode(GL_TEXTURE);
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
    GL_CHECK_ERROR;
    glDeleteTextures(1, &tempTex);
    GL_CHECK_ERROR;
}
//-----------------------------------------------------------------------------  
// blitFromMemory doing hardware trilinear scaling
void GLESTextureBuffer::blitFromMemory(const U2PixelBox &src_orig, const U2Image::Box &dstBox)
{
    // Fall back to normal GLHardwarePixelBuffer::blitFromMemory in case 
    // - FBO is not supported
    // - Either source or target is luminance due doesn't looks like supported by hardware
    // - the source dimensions match the destination ones, in which case no scaling is needed
    if(!GL_OES_framebuffer_object ||
       U2PixelUtil::isLuminance(src_orig.format) ||
       U2PixelUtil::isLuminance(mFormat) ||
       (src_orig.getWidth() == dstBox.getWidth() &&
        src_orig.getHeight() == dstBox.getHeight() &&
        src_orig.getDepth() == dstBox.getDepth()))
    {
        GLESHardwarePixelBuffer::blitFromMemory(src_orig, dstBox);
        return;
    }
    
    if(!mBuffer.contains(dstBox))
        U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS, "Destination box out of range",
                    "GLESTextureBuffer::blitFromMemory");
    // For scoped deletion of conversion buffer
    U2DataStreamMemoryPtr buf;
    U2PixelBox src;
    
    // First, convert the srcbox to a OpenGL compatible pixel format
    if(U2GLESPixelUtil::getGLOriginFormat(src_orig.format) == 0)
    {
        // Convert to buffer internal format
        buf.bind(U2_NEW U2DataStreamMemory(U2PixelUtil::getMemorySize(src_orig.getWidth(), src_orig.getHeight(), src_orig.getDepth(),
                                                               mFormat)));
        src = U2PixelBox(src_orig.getWidth(), src_orig.getHeight(), src_orig.getDepth(), mFormat, buf->getPtr());
        U2PixelUtil::bulkPixelConversion(src_orig, src);
    }
    else
    {
        // No conversion needed
        src = src_orig;
    }
    
    // Create temporary texture to store source data
    GLuint id;
    GLenum target = GL_TEXTURE_2D;
    GLsizei width = U2GLESPixelUtil::optionalPO2(src.getWidth());
    GLsizei height = U2GLESPixelUtil::optionalPO2(src.getHeight());
    GLenum format = U2GLESPixelUtil::getClosestGLInternalFormat(src.format);
    GLenum datatype = U2GLESPixelUtil::getGLOriginDataType(src.format);
    // Generate texture name
    glGenTextures(1, &id);
    GL_CHECK_ERROR;
    
    // Set texture type
    glBindTexture(target, id);
    GL_CHECK_ERROR;
    
    // Set automatic mipmap generation; nice for minimisation
    glTexParameteri(target, GL_GENERATE_MIPMAP, GL_TRUE );
    GL_CHECK_ERROR;

    // Allocate texture memory
    glTexImage2D(target, 0, format, width, height, 0, format, datatype, 0);
    GL_CHECK_ERROR;

    // GL texture buffer
    GLESTextureBuffer tex(U2StringUtil::BLANK, target, id, width, height, format, src.format,
                          0, 0, (Usage)(TU_AUTOMIPMAP|HBU_STATIC_WRITE_ONLY), false, false, 0);
    
    // Upload data to 0,0,0 in temporary texture
    U2Image::Box tempTarget(0, 0, 0, src.getWidth(), src.getHeight(), src.getDepth());
    tex.upload(src, tempTarget);
    
    // Blit
    blitFromTexture(&tex, tempTarget, dstBox);
    
    // Delete temp texture
    glDeleteTextures(1, &id);
    GL_CHECK_ERROR;
}

/**********************@@@@@@@@@@@@@@@@@@@@@
RenderTexture *GLESTextureBuffer::getRenderTarget(size_t zoffset)
{
    assert(mUsage & TU_RENDERTARGET);
    assert(zoffset < mDepth);
    return mSliceTRT[zoffset];
}
*/

void GLESTextureBuffer::buildMipmaps(const U2PixelBox &data)
{
    int width;
    int height;
    int logW;
    int logH;
    int level;
    U2PixelBox scaled = data;
    scaled.data = data.data;
    scaled.left = data.left;
    scaled.right = data.right;
    scaled.top = data.top;
    scaled.bottom = data.bottom;
    scaled.front = data.front;
    scaled.back = data.back;

    width = data.getWidth();
    height = data.getHeight();

    logW = computeLog(width);
    logH = computeLog(height);
    level = (logW > logH ? logW : logH);

    for (int mip = 0; mip <= level; mip++)
    {
        GLenum glFormat = U2GLESPixelUtil::getGLOriginFormat(scaled.format);
        GLenum dataType = U2GLESPixelUtil::getGLOriginDataType(scaled.format);

        glTexImage2D(GL_TEXTURE_2D,
                     mip,
                     glFormat,
                     width, height,
                     0,
                     glFormat,
                     dataType,
                     scaled.data);

        GL_CHECK_ERROR;

        if (mip != 0)
        {
            delete[] (u2uint8*) scaled.data;
            scaled.data = 0;
        }

        if (width > 1)
        {
            width = width / 2;
        }

        if (height > 1)
        {
            height = height / 2;
        }

        int sizeInBytes = U2PixelUtil::getMemorySize(width, height, 1,
                                                   data.format);
        scaled = U2PixelBox(width, height, 1, data.format);
        scaled.data = new u2uint8[sizeInBytes];
        U2Image::scale(data, scaled, U2Image::FILTER_LINEAR);
    }
}

//********* GLESRenderBuffer
//----------------------------------------------------------------------------- 
GLESRenderBuffer::GLESRenderBuffer(GLenum format, size_t width, size_t height, GLsizei numSamples):
GLESHardwarePixelBuffer(width, height, 1, U2GLESPixelUtil::getClosestOGREFormat(format, PF_A8R8G8B8),HBU_WRITE_ONLY)
{
    mGLInternalFormat = format;
    // Generate renderbuffer
    glGenRenderbuffersOES(1, &mRenderbufferID);
    GL_CHECK_ERROR;
    // Bind it to FBO
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, mRenderbufferID);
    GL_CHECK_ERROR;
    
    // Allocate storage for depth buffer
    if (numSamples <= 0)
    {
        glRenderbufferStorageOES(GL_RENDERBUFFER_OES, format,
                                 width, height);
        GL_CHECK_ERROR;
    }
}
//----------------------------------------------------------------------------- 
GLESRenderBuffer::~GLESRenderBuffer()
{
    // Generate renderbuffer
    glDeleteRenderbuffersOES(1, &mRenderbufferID);
    GL_CHECK_ERROR;
}
//-----------------------------------------------------------------------------  
void GLESRenderBuffer::bindToFramebuffer(GLenum attachment, size_t zoffset)
{
    assert(zoffset < mDepth);
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, attachment,
                                 GL_RENDERBUFFER_OES, mRenderbufferID);
    GL_CHECK_ERROR;
}

